#include "nw_definitions.h"
#include "../transport_layer/tr_common.h"

#include <cnetsupport.h>

#define link_cost_table(link) (a_ht_dest_link_costs[link - 1])
#define link_cost(link) (a_link_costs[link-1])


#define MAX_HOPS 70



VECTOR 		a_avail_destinations;
HASHTABLE   ht_destinations; //routing table (destination, outgoing link)
HASHTABLE* 	a_ht_dest_link_costs; //array for all avaible links containing hash tables(destination, cost)

CnetTime routing_timer;
CnetTime reboot_timer1, reboot_timer2;

/*
 * ROUTING BEGIN
 * ---------------------------------------------------------------------------------------------------------------------
 */
 
 
//creates a routing-specific packet where the source and destination addresses are the same
static void NW_create_disc_pkt(unsigned char destination, int cost, int except_linkno, int cnt_hops)
{	
	//escape if the discovery packet traveled too much; TODO: find out if needed
	if(cnt_hops >= MAX_HOPS) {
		return;
	} else {
	}

	
	PACKET pkt;
	pkt.src = nodeinfo.address;
	pkt.dest = nodeinfo.address;
	
	/*
	 * routing information
	 */
	pkt.len = 3* sizeof(char);
	//destination and cost
	pkt.msg.data[0] = destination;
	pkt.msg.data[1] = cost;
	pkt.msg.data[2] = ++cnt_hops;
	//pkt.msg.data[3] = ++nodeinfo;

	for(int linkno = 1;linkno <= nodeinfo.nlinks; ++linkno) //sending out discovery packets to all neighbors
	{
		if(linkno != except_linkno) { //horizon splitting
			DL_buffer_packet(&pkt,linkno);
			DL_buffer_packet(&pkt,linkno);
			DL_buffer_packet(&pkt,linkno);
		}
	}
}	

//method determining which outgoing link to take; will be done during 3. milestone
void NW_route_packet(PACKET *pkt)
{
	int* ptr_linkno = NULL;
	size_t len;
	char destination[2] = { (char)(pkt->dest), 0 };

	ptr_linkno = hashtable_find(ht_destinations, destination, &len);

        
	if(ptr_linkno && (head_crc(pkt) == pkt->crc_head)) {						//route found and packet alright
//		fprintf(stderr, "%s NW_route_packet(): src=%u, dest=%u, len=%u\n", nodeinfo.nodename, (unsigned int)pkt->src, (unsigned int)pkt->dest, (unsigned int)pkt->len);
		DL_buffer_packet(pkt, *ptr_linkno);
	} /*else
		fprintf(stderr, "%s NW_route_packet(): crc_head=0x%x, head_crc=0x%x\n", nodeinfo.nodename, pkt->crc_head, head_crc(pkt));*/
	
}
/*
 * ROUTING END
 * ---------------------------------------------------------------------------------------------------------------------
 */

static void NW_reboot_timeouts(CnetEvent ev,CnetTimerID timer,CnetData data)
{
	if(data == 0){
		NW_create_disc_pkt(nodeinfo.address, 0, 0, 0);
		NW_create_disc_pkt(nodeinfo.address, 0, 0, 0);
		NW_create_disc_pkt(nodeinfo.address, 0, 0, 0);


	} else {
//		fprintf(stderr, "%s NW_reboot_timeouts(): enable app layer\n", nodeinfo.nodename);
		CNET_enable_application(ALLNODES);
		route_ready = true;
	}
}	 

static int index_updated_dest = 1;
//periodically, the node exchanges information with its neighbors
static void NW_routing_updates(CnetEvent ev,CnetTimerID timer,CnetData data)
{
	int num_dest = vector_nitems(a_avail_destinations);
	if(index_updated_dest > (num_dest)) index_updated_dest = 1; //reset

	int* ptr_link;
	size_t len;
	int* cost;
	char* destination;

	
	 int items = 0;
	 int sum = 0;
     for(unsigned int link = 1; link <= nodeinfo.nlinks; ++link) {

		OUT_LINK_DATA* iter_out = &a_out_link_data[link - 1];        
        items = queue_nitems(iter_out->buffer);
        sum += items;
                
     }
     
    //congestion warning broadcast
    if(sum > 80 * nodeinfo.nlinks){	
		for(int i = 0; i < vector_nitems(a_avail_destinations); i++)
		{
			destination = vector_peek(a_avail_destinations, i, &len);
			ptr_link = hashtable_find(ht_destinations,destination, &len);
			cost = hashtable_find(link_cost_table(*ptr_link),destination, &len);
			NW_create_disc_pkt((unsigned char)destination[0], *cost  , 0, 0);
		}
	} else {
		NW_create_disc_pkt(nodeinfo.address, 0, 0, 0);
	}
	
	
	//triggering next update  
	routing_timer = (CnetTime) 50000000;
	CNET_start_timer(EV_TIMER4,routing_timer, 0);
	index_updated_dest++; //another dest next time
}		
